package main

import (
	"bytes"
	"fmt"
	"io"
	"net"

	"../pb"
	"../util"
	log "github.com/kpango/glg"
	"golang.org/x/net/context"
	"google.golang.org/grpc/peer"
)

const (
	FILE_NOTFINISHED = int64(1)
	FILE_FINISHED    = int64(2)

	OK    = int64(4)
	ERROR = int64(5)
)

type RfdService struct {
	bufferSize int
	resendNum  int
}

func (rs *RfdService) FileTransfer(srv pb.RfdService_FileTransferServer) error {
	// recv request
	req, err := srv.Recv()
	if err != nil {
		return err
	}
	ip, err := getClientIP(srv.Context())
	if err != nil {
		log.Error(err.Error())
	} else {
		log.Infof("Server.FileTransfer: Receive request from %s, FileName: %s.",
			ip, req.FilePath)
	}

	// get data from docService
	result, err := GetDoc(req.FilePath, req.LatnId)
	if err != nil {
		log.Errorf("Server.FileTransfer.DocService: fail to get file %s, latnId: %s, desc: %v", req.FilePath, req.LatnId, err)
		return err
	}
	log.Infof("Server.FileTransfer.DocService: get file success: %s. latnId: %s", req.FilePath, req.LatnId)

	// split the data to control the transfer data size
	// response to client
	reader := bytes.NewReader(result)
	buffer := make([]byte, rs.bufferSize)
	for {
		n, err := reader.Read(buffer)
		if err == io.EOF {
			resp := &pb.FileTransferResponse{
				State: FILE_FINISHED,
			}
			return rs.Resend(srv, resp)
		}
		data := buffer[:n]
		resp := &pb.FileTransferResponse{
			FileData: data,
			CheckSum: util.MD5(data),
			State:    FILE_NOTFINISHED,
		}
		err = rs.Resend(srv, resp)
		if err != nil {
			return err
		}
	}
	return nil
}

// resend if failed, resend at most 5 times
func (rs *RfdService) Resend(srv pb.RfdService_FileTransferServer, resp *pb.FileTransferResponse) error {
	for i := 0; i <= rs.resendNum; i++ {
		err := srv.Send(resp)
		if err != nil {
			log.Errorf("srv send,error:%v", err)
			return err
		}
		req, err := srv.Recv()
		if err != nil {
			log.Errorf("srv recv,error:%v", err)
			return err
		}
		if req.State == OK {
			return nil
		}
	}
	return fmt.Errorf("resend 5 times and fail.")
}

func getClientIP(ctx context.Context) (string, error) {
	pr, ok := peer.FromContext(ctx)
	if !ok {
		return "", fmt.Errorf("Server.getClinetIP: invoke FromContext() failed")
	}
	if pr.Addr == net.Addr(nil) {
		return "", fmt.Errorf("Server.getClinetIP: peer.Addr is nil")
	}
	return pr.Addr.String(), nil
}
